package de.gzork.zmpp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;

import org.zmpp.base.DefaultMemory;
import org.zmpp.base.Memory;
import org.zmpp.iff.DefaultFormChunk;
import org.zmpp.iff.FormChunk;
import org.zmpp.iff.WritableFormChunk;
import org.zmpp.vm.SaveGameDataStore;

import com.google.appengine.repackaged.com.google.common.util.Base64;
import com.google.appengine.repackaged.com.google.common.util.Base64DecoderException;

import de.gzork.model.SaveGame;
import de.gzork.model.SmallBlob;
import de.gzork.server.EMF;

public class DBSaveGameDataStore implements SaveGameDataStore {

	private String username;
	private Logger LOGGER = Logger.getAnonymousLogger();

	public DBSaveGameDataStore(String username) {
		this.username = username;
	}

	private DBSaveGameDataStore() {
		// don't use this!
	}

	@Override
	public FormChunk retrieveFormChunk() {
		EntityManager em = EMF.get().createEntityManager();
		SaveGame savegame = null;
		try {
			savegame = em.find(SaveGame.class, username);
		} catch (NoSuchElementException e) {
			LOGGER.log(Level.WARNING, e.getMessage());
			System.out.println(e.getMessage());
		}
		if (savegame != null) {
			// initialize OneToMany since the EAGER-Annotations seems to be
			// ignored
			savegame.getData();
			LOGGER.log(Level.INFO, "SaveGame for \"" + username + "\" found");
			System.out.println("SaveGame for \"" + username + "\" found");
			byte[] data = createByteArray(savegame.getData());
			Memory memory = new DefaultMemory(data);
			FormChunk chunk = null;
			try {
				chunk = new DefaultFormChunk(memory);
			} catch (IOException e) {
				LOGGER.log(Level.SEVERE, e.getMessage());
			} catch (ArrayIndexOutOfBoundsException e) {
				LOGGER.log(Level.SEVERE, e.getMessage());
				LOGGER.log(Level.SEVERE, "probably empty, finished or stopped savegame");
			}
			em.close();
			return chunk;
		}
		em.close();
		return null;
	}

	@Override
	public boolean saveFormChunk(WritableFormChunk formChunk) {
		LOGGER.log(Level.INFO, "Saving, formChunk.getSize()= "
				+ formChunk.getSize() + " formChunk.getBytes().length = "
				+ formChunk.getBytes().length);
		EntityManager em = EMF.get().createEntityManager();
		SaveGame savegame = null;
		try {
			savegame = em.find(SaveGame.class, username);
		} catch (NoSuchElementException e) {
			LOGGER.log(Level.WARNING, e.getMessage());
		}

		em.getTransaction().begin();
		if (savegame != null) {
			for (SmallBlob sb : savegame.getData()) {
				em.remove(sb);
			}
			em.remove(savegame);
		}

		List<SmallBlob> smallblobs = createSmallBlobs(formChunk.getBytes());
		savegame = new SaveGame(username, smallblobs);
		em.persist(savegame);

		em.getTransaction().commit();
		em.clear();
		em.close();

		return true;
	}

	private List<SmallBlob> createSmallBlobs(byte[] data) {
		int datalength = 500;
		List<SmallBlob> smallblobs = new ArrayList<SmallBlob>();
		byte[] saveData = data;
		int offset = 0;
		Long counter = 0l;
		while (offset < saveData.length) {
			byte[] smalldata = new byte[saveData.length - offset > datalength ? datalength
					: saveData.length - offset];
			for (int i = 0; i < smalldata.length; i++) {
				smalldata[i] = saveData[offset];
				offset += 1;
			}
			SmallBlob smallblob = new SmallBlob(counter, smalldata);
			counter++;
			smallblobs.add(smallblob);
		}

		return smallblobs;
	}

	private byte[] createByteArray(List<SmallBlob> smallblobs) {
		int size = 0;

		Comparator<SmallBlob> compBlob = new Comparator<SmallBlob>() {
			@Override
			public int compare(SmallBlob sm1, SmallBlob sm2) {
				if (sm1.getOrder() < sm2.getOrder()) {
					return -1;
				} else if (sm1.getOrder() > sm2.getOrder()) {
					return 1;
				}
				return 0;
			}
		};

		Collections.sort(smallblobs, compBlob);

		for (SmallBlob smallblob : smallblobs) {
			byte[] decoded = null;
			decoded = smallblob.getData();
			size += decoded.length;
		}
		byte[] data = new byte[size];

		int offset = 0;
		for (SmallBlob smallblob : smallblobs) {
			byte[] decoded = null;
			decoded = smallblob.getData();
			for (int i = 0; i < decoded.length; i++) {
				data[offset] = decoded[i];
				offset += 1;
			}
		}

		return data;
	}
}
